'''
Created on 24.03.2012

@author: myaut
'''

from threading import Condition

TPOOL_JOIN_TIMEOUT = 0.1

class ThreadPool:
    '''Implements pool of threads. Unlike traditional method with Queues provides
    objects that are shared between threads dispatched to same slot
    
    Useful for IO connections ( @see salsa.util.io )
    
    Example:
    
    class MyThread(Thread):
        def __init__(self, tp):
            self.tp = tp
        
        def sobj_attach(self, sobj):
            # Attaches to shared object
            pass
        
        def run(self):
            # Main code
            
            tp.notify()
    
    class SharedObject:
        def __init__(self):
            pass
        
        def stop(self):
            # Clean up shared obj resources
            pass
    
    tp = ThreadPool(10, sobj_class=SharedObject)
    
    tp.dispatch(MyThread())
    tp.join()
    '''
    class Slot:
        def __init__(self, slotid, shared_obj):
            self.slotid = slotid
            
            self.thread = None
            self.shared_obj = shared_obj
        
        def available(self):
            return self.thread == None or self.thread.is_alive() == False
        
        def stop_sobj(self):
            if self.shared_obj:
                self.shared_obj.stop()
        
        def put_thread(self, new_thread):
            self.thread = new_thread
            
            if self.shared_obj:
                self.thread.sobj_attach(self.shared_obj)
    
    def __init__(self, max_threads, sobj_class = None):
        self.max_threads = max_threads
        
        self.pool = []
        
        for slotid in range(max_threads):
            sobj = None
            
            if sobj_class:
                sobj = sobj_class()
            
            self.pool.append(ThreadPool.Slot(slotid, sobj))
            
        self.cv = Condition()
    
    def available_slots(self):
        return [slot.available() for slot in self.pool]
    
    def notify(self):
        with self.cv:
            self.cv.notify()
    
    def dispatch(self, thread):
        while not any(self.available_slots()):
            # Wait until slot will become available
            with self.cv:
                self.cv.wait(TPOOL_JOIN_TIMEOUT)
        
        # Select slot and dispatch thread
        avail_slots = self.available_slots()
        
        for (slot_id, avail) in enumerate(avail_slots):
            if avail:
                self.pool[slot_id].put_thread(thread)
                thread.start()
                
                break
            
    def join(self):
        '''Blocks until all threads are finished'''
        while not all(self.available_slots()):
            with self.cv:
                self.cv.wait(TPOOL_JOIN_TIMEOUT)
        
        for slot in self.pool:
            slot.stop_sobj()
        